//
//  Created by Дмитрий Бахвалов on 13.07.15.
//  Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved.
//

#ifndef __HIREDIS_MACOSX_H__
#define __HIREDIS_MACOSX_H__

#include <CoreFoundation/CoreFoundation.h>

#include "../hiredis.h"
#include "../async.h"

typedef struct
{
    redisAsyncContext* context;
    CFSocketRef socketRef;
    CFRunLoopSourceRef sourceRef;
} RedisRunLoop;

static int freeRedisRunLoop(RedisRunLoop* redisRunLoop)
{
    if (redisRunLoop != NULL)
    {
        if (redisRunLoop->sourceRef != NULL)
        {
            CFRunLoopSourceInvalidate(redisRunLoop->sourceRef);
            CFRelease(redisRunLoop->sourceRef);
        }
        if (redisRunLoop->socketRef != NULL)
        {
            CFSocketInvalidate(redisRunLoop->socketRef);
            CFRelease(redisRunLoop->socketRef);
        }
        hi_free(redisRunLoop);
    }
    return REDIS_ERR;
}

static void redisMacOSAddRead(void* privdata)
{
    RedisRunLoop* redisRunLoop = (RedisRunLoop*)privdata;
    CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack);
}

static void redisMacOSDelRead(void* privdata)
{
    RedisRunLoop* redisRunLoop = (RedisRunLoop*)privdata;
    CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack);
}

static void redisMacOSAddWrite(void* privdata)
{
    RedisRunLoop* redisRunLoop = (RedisRunLoop*)privdata;
    CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack);
}

static void redisMacOSDelWrite(void* privdata)
{
    RedisRunLoop* redisRunLoop = (RedisRunLoop*)privdata;
    CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack);
}

static void redisMacOSCleanup(void* privdata)
{
    RedisRunLoop* redisRunLoop = (RedisRunLoop*)privdata;
    freeRedisRunLoop(redisRunLoop);
}

static void
redisMacOSAsyncCallback(CFSocketRef __unused s, CFSocketCallBackType callbackType, CFDataRef __unused address, const void __unused* data, void* info)
{
    redisAsyncContext* context = (redisAsyncContext*)info;

    switch (callbackType)
    {
    case kCFSocketReadCallBack:
        redisAsyncHandleRead(context);
        break;

    case kCFSocketWriteCallBack:
        redisAsyncHandleWrite(context);
        break;

    default:
        break;
    }
}

static int redisMacOSAttach(redisAsyncContext* redisAsyncCtx, CFRunLoopRef runLoop)
{
    redisContext* redisCtx = &(redisAsyncCtx->c);

    /* Nothing should be attached when something is already attached */
    if (redisAsyncCtx->ev.data != NULL)
        return REDIS_ERR;

    RedisRunLoop* redisRunLoop = (RedisRunLoop*)hi_calloc(1, sizeof(RedisRunLoop));
    if (redisRunLoop == NULL)
        return REDIS_ERR;

    /* Setup redis stuff */
    redisRunLoop->context = redisAsyncCtx;

    redisAsyncCtx->ev.addRead = redisMacOSAddRead;
    redisAsyncCtx->ev.delRead = redisMacOSDelRead;
    redisAsyncCtx->ev.addWrite = redisMacOSAddWrite;
    redisAsyncCtx->ev.delWrite = redisMacOSDelWrite;
    redisAsyncCtx->ev.cleanup = redisMacOSCleanup;
    redisAsyncCtx->ev.data = redisRunLoop;

    /* Initialize and install read/write events */
    CFSocketContext socketCtx = {0, redisAsyncCtx, NULL, NULL, NULL};

    redisRunLoop->socketRef = CFSocketCreateWithNative(NULL, redisCtx->fd, kCFSocketReadCallBack | kCFSocketWriteCallBack, redisMacOSAsyncCallback, &socketCtx);
    if (!redisRunLoop->socketRef)
        return freeRedisRunLoop(redisRunLoop);

    redisRunLoop->sourceRef = CFSocketCreateRunLoopSource(NULL, redisRunLoop->socketRef, 0);
    if (!redisRunLoop->sourceRef)
        return freeRedisRunLoop(redisRunLoop);

    CFRunLoopAddSource(runLoop, redisRunLoop->sourceRef, kCFRunLoopDefaultMode);

    return REDIS_OK;
}

#endif
